# Python 趣味编程：第 2 章
# 拓展问题 2.4 病毒感染的模拟
# --------------------------
# 程序名: ex02-4-epidemic.py

from tkinter import *
from dataclasses import dataclass
import time
import random      # 在这个课题中，使用了“随机数”

# 参数的初始化
NUM_PERSONS = 10   # 规定人数
DURATION = 0.01
NORMAL_COLOR = "black"
EPI_COLOR = "red"

@dataclass
class Person:
    id: int
    x: int
    y: int
    w: int
    h: int
    vx: int
    vy: int
    infected: bool

@dataclass
class Border:
    left: int
    right: int
    top: int
    bottom: int

# 绘制人，返回生成的dataclass。
def make_person(x, y, w, h, vx, vy, infected=False):
    if infected:
        c = EPI_COLOR
    else:
        c = NORMAL_COLOR
    id = canvas.create_rectangle(x, y, x + w, y + h, fill=c, outline=c)
    return Person(id, x, y, w, h, vx, vy, infected)

# 把人转移开
def move_person(person):
    person.x += person.vx
    person.y += person.vy

# 墙壁的绘制  
def make_walls(ox, oy, width, height):
    canvas.create_rectangle(ox, oy, ox + width, oy + height)

# 人的重绘
def redraw_person(person):
    canvas.coords(person.id, person.x, person.y,
                  person.x + person.w, person.y + person.h)
    if person.infected:
        c = EPI_COLOR
    else:
        c = NORMAL_COLOR
    canvas.itemconfigure(person.id, fill=c)
    canvas.itemconfigure(person.id, outline=c)

# 准备“人们”
def prepare_persons():
    persons = []
    for i in range(NUM_PERSONS):
        if i==0:      # 将最前面的数据视为“感染”
            infected = True
        else:
            infected = False
        w = random.randint(5, 10)   # 生成随机数
        x = random.randint(border.left, border.right - w)
        h = random.randint(5, 10)
        y = random.randint(border.top, border.bottom - h)
        vx = random.randint(-3, 3)
        vy = random.randint(-3, 3)
        persons.append(make_person(x, y, w, h, vx, vy, infected))
    return persons

# 检查“人”与“人”之间的联系
# 只要其中一方被感染，并与之接触，就被称为“感染”
def check_contact(p1, p2):
    # 同样是“人”，什么都不做
    if p1.id == p2.id: return
    # 如果两人中有一人被感染，并有接触，双方都处于感染状态
    if p1.infected or p2.infected: 
         if (p1.x < p2.x + p2.w \
            and p1.x + p1.w > p2.x \
            and p1.y < p2.y + p2.h \
            and p1.y + p2.h > p2.y):
            p1.infected = p2.infected = True

tk = Tk()
canvas = Canvas(tk, width=500, height=400, bd=0, bg="white")
canvas.pack()
tk.update()

# 给出墙壁的坐标(left, right, top, bottom)
border = Border(100, 400, 100, 300)

make_walls(
    border.left,
    border.top,
    border.right - border.left,
    border.bottom - border.top
    )

persons = prepare_persons()   # 这里，对“人们”进行初始设定。

while True:
    num_infected = 0
    for person in persons:  # 针对所有人实施
        move_person(person)  # 人的移动
        if (person.x + person.vx < border.left \
            or person.x + person.w >= border.right):
            person.vx = -person.vx
        if (person.y + person.vy <  border.top \
            or person.y + person.h  >= border.bottom):
            person.vy = -person.vy
        for p in persons:  # 把包括自己在内的所有人都当作“对方”来检查
            check_contact(p, person)  # 与对方的接触确认
        redraw_person(person)
        if person.infected:
            num_infected = num_infected + 1
    tk.update()
    # 如果所有人都感染了，就结束了
    if num_infected == NUM_PERSONS: break
    time.sleep(DURATION)
